The SHBrowseForFolder API function
brings up a dialog window which allows the user to select a shell folder,
which can include directories, printers, networks etc etc. Not only is it
powerful, but it's also a pretty looking thing, utilising a treeview
control to enumerate the shell folders. One of the key things to note
about this function and other shell functions is the way that a folder is
identified. Each shell folder has an item identifier, which is unique
among items within its parent folder. Since all of its ancestor folders
(parent, grandparent, etc etc.) have their own item ID's, a shell folder
is thus uniquely identified by a list of item identifiers, which is called
an item ID list. Because shell folders can be many things, they are implemented as COM objects which through the IShellFolder interface can carry out various actions (the interface's methods). One of these actions is to convert an item ID list into a directory name (technically, namespace). According to the Win32API docs, whenever an object's IShellFolder interface is obtained, the interface must eventually be freed by calling its Release method. The neat thing is that Delphi 3.x does this for you! On the other hand, any item ID lists you create must be freed by you, via the shell allocator, which is an IMalloc interface. A call to the SHGetMalloc function will retrieve a pointer to the shell allocator. So without further ado, here is a function which wraps up the SHBrowseForFolder API function, including copious amounts of comments which describe what the hell is going on: function BrowseDirectoryMP(Handle : HWnd; InitialDir : String) : String; {Handle is the parent of the BrowseDirectory dialog window, while InitialDir is the directory which is selected when the dialog window is opened.} var MyShellMalloc : IMalloc; // IMalloc task allocator MyBrowseInfo : TBrowseInfo; DestDir : String; shBuff : PChar; MyItemIDList, TheItemIDList, SelectionIDList : PItemIDList; ShellFolder: IShellFolder; // the powerful IShellFolder interface OLEStr: array[0..MAX_PATH] of TOLEChar; // need to use OLE strings Eaten, Attr: ULONG; Success : Boolean; begin {This call is necessary as it retrieves a pointer to the IMalloc interface of the Win95 shell, which in turn is used to free memory that was allocated by the shell; something that certain shell functions require.} SHGetMalloc(MyShellMalloc); {Initialise destination string to be empty and set its length to MAX_PATH} DestDir := ''; SetLength(DestDir, MAX_PATH); {The pszDisplayName member of the TSHBrowseInfo structure needs to be allocated space via the shell's IMalloc interface} shBuff := PChar(MyShellMalloc.Alloc(MAX_PATH)); {if allocation is sucessful then proceed} if assigned(shBuff) then begin try {Retrieve a pointer to an item identifier list specifying the location of the My Computer virtual folder relative to the desktop folder} SHGetSpecialFolderLocation(Handle, CSIDL_DRIVES, MyItemIDList); try {MyItemIDList will be the root folder for the Browse Directory dialog. But what if you want a directory to be initially selected? Well, you first need to retrieve its item identifier. We do this via the IShellFolder interface for the desktop folder} if SHGetDesktopFolder(ShellFolder) = NO_ERROR then begin {The ParseDisplayName method will do the trick. But we need to make sure our directory string is a null-terminated Unicode string We use the StringToWideChar function to convert it properly} if ShellFolder.ParseDisplayName(Handle, nil, StringToWideChar(InitialDir, OLEStr, MAX_PATH), Eaten, SelectionIDList, Attr) = NO_ERROR then Success := True else Success := False; end else Success := False; {Before calling the function which brings up the dialog editor, we need to fill in the members of the relevant structure TBrowseInfo} try with MyBrowseInfo do begin hwndOwner := Handle; // owner of dialog window pidlRoot := MyItemIDList; // specified the root folder pszDisplayName := shBuff; // this receives the selected folder lpszTitle := PChar('Please Select Directory'); // dialog title ulFlags := BIF_RETURNONLYFSDIRS; // return only file-system dirs if Success then begin {OK, this is the second part of selecting the initial directory of the browse dialog. lpfn points to a callback function, which the dialog window calls whenever events occur. One of the events is the opening of the dialog window. When this occurs we will send a selection message to browse dialog window, which will select the intial directory of our choice} lpfn := BrowseCallbackProc; {lParam gets passed to the callback function. It represents the item identifier (obtained above) of the directory we wish to select} lParam := Integer(SelectionIDList); end else begin {If, for whatever reason we couldn't obtain item identifier we set the callback function to nil} lpfn := nil; lParam := 0; end; end; {Here is where the dialog is called up for display, finally! The return result is the item identifier for the directory which was chosen by the user} TheItemIDList := SHBrowseForFolder(MyBrowseInfo); try {Convert the item identfier into a directory name} if SHGetPathFromIDList(TheItemIDList, shBuff) then DestDir := shBuff; finally {It is very important that free the item identfiers} MyShellMalloc.Free(TheItemIDList); // Clean-up end; finally MyShellMalloc.Free(SelectionIDList); // Clean-up. end; finally MyShellMalloc.Free(MyItemIDList); // Clean-up. end; finally MyShellMalloc.Free(shBuff); // Clean-up. end; end; Result := String(PChar(DestDir)); // the result!! end;And here is the callback function: function BrowseCallbackProc(Wnd: HWnd; Msg: UINT; lParam: LPARAM; lData: LPARAM): integer; stdcall; begin Result := 0; {upon startup, set the selection to the intial directory desired} if Msg = BFFM_INITIALIZED then begin SendMessage(Wnd, BFFM_SETSELECTION, WPARAM(False), lData); end; {if} end; |